{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Ndo4ERqnwQOU"
      },
      "source": [
        "##### Copyright 2020 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "MTKwbguKwT4R"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xfNT-mlFwxVM"
      },
      "source": [
        "# Сверточный вариационный автоэнкодер(Convolutional VAE)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0TD5ZrvEMbhZ"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://www.tensorflow.org/tutorials/generative/cvae\">\n",
        "    <img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" />\n",
        "    Смотрите на TensorFlow.org</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ru/tutorials/generative/cvae.ipynb\">\n",
        "    <img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />\n",
        "    Запустите в Google Colab</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ru/tutorials/generative/cvae.ipynb\">\n",
        "    <img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />\n",
        "    Изучайте код на GitHub</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ru/tutorials/generative/cvae.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\" />Скачайте ноутбук</a>\n",
        "  </td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8a2322303a3f"
      },
      "source": [
        "Note: Вся информация в этом разделе переведена с помощью русскоговорящего Tensorflow сообщества на общественных началах. Поскольку этот перевод не является официальным, мы не гарантируем что он на 100% аккуратен и соответствует [официальной документации на английском языке](https://www.tensorflow.org/?hl=en). Если у вас есть предложение как исправить этот перевод, мы будем очень рады увидеть pull request в [tensorflow/docs](https://github.com/tensorflow/docs) репозиторий GitHub. Если вы хотите помочь сделать документацию по Tensorflow лучше (сделать сам перевод или проверить перевод подготовленный кем-то другим), напишите нам на [docs-ru@tensorflow.org list](https://groups.google.com/a/tensorflow.org/forum/#!forum/docs-ru)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ITZuApL56Mny"
      },
      "source": [
        "Этот руководство демонстрирует, как обучить вариационный автоэнкодер (VAE) ([1](https://arxiv.org/abs/1312.6114), [2](https://arxiv.org/abs/1401.4082)) на наборе данных MNIST. \n",
        "VAE - это вероятностный тип автоэнкодера, модель, которая принимает входные данные большого размера и сжимает их в меньшее представление. В отличие от традиционного автоенкодера, который отображает входные данные в скрытый вектор, VAE отображает входные данные в параметры распределения вероятностей, такие как среднее значение и дисперсия Гаусса. Такой подход создает непрерывное структурированное скрытое пространство, которое полезно для генерации изображений.\n",
        "\n",
        "![Скрытое пространство CVAE](images/cvae_latent_space.jpg)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "e1_Y75QXJS6h"
      },
      "source": [
        "## Установка"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "228e86a2f459"
      },
      "outputs": [],
      "source": [
        "!pip -V"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "P-JuIu2N_SQf"
      },
      "outputs": [],
      "source": [
        "!pip install tensorflow-probability\n",
        "\n",
        "# to generate gifs\n",
        "!pip install imageio\n",
        "!pip install git+https://github.com/tensorflow/docs"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "YfIk2es3hJEd"
      },
      "outputs": [],
      "source": [
        "from IPython import display\n",
        "\n",
        "import glob\n",
        "import imageio\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "import PIL\n",
        "import tensorflow as tf\n",
        "import tensorflow_probability as tfp\n",
        "import time"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "iYn4MdZnKCey"
      },
      "source": [
        "## Загрузка датасета MNIST\n",
        "Каждое изображение MNIST изначально представляет собой вектор из 784 целых чисел, каждое из которых находится в диапазоне от 0 до 255 и представляет интенсивность пикселя. Мы нормализуем каждый пиксель изображения и бинаризуем набор данных."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "a4fYMGxGhrna"
      },
      "outputs": [],
      "source": [
        "(train_images, _), (test_images, _) = tf.keras.datasets.mnist.load_data()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "NFC2ghIdiZYE"
      },
      "outputs": [],
      "source": [
        "def preprocess_images(images):\n",
        "  images = images.reshape((images.shape[0], 28, 28, 1)) / 255.\n",
        "  return np.where(images > .5, 1.0, 0.0).astype('float32')\n",
        "\n",
        "train_images = preprocess_images(train_images)\n",
        "test_images = preprocess_images(test_images)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "S4PIDhoDLbsZ"
      },
      "outputs": [],
      "source": [
        "train_size = 60000\n",
        "batch_size = 32\n",
        "test_size = 10000"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PIGN6ouoQxt3"
      },
      "source": [
        "## Используйте *tf.data* для пакетной обработки и перемешивания данных"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "-yKCCQOoJ7cn"
      },
      "outputs": [],
      "source": [
        "train_dataset = (tf.data.Dataset.from_tensor_slices(train_images)\n",
        "                 .shuffle(train_size).batch(batch_size))\n",
        "test_dataset = (tf.data.Dataset.from_tensor_slices(test_images)\n",
        "                .shuffle(test_size).batch(batch_size))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "THY-sZMiQ4UV"
      },
      "source": [
        "## Определение нейронной сети енкодера и декодера с помощью *tf.keras.Sequential*\n",
        "\n",
        "В нашем примере VAE мы используем две небольшие ConvNets для енкодера и декодера. В литературе эти сети также называются моделями вывода/распознавания и генеративными моделями соответственно. Мы используем `tf.keras.Sequential` для упрощения реализации. Пусть $x$ и $z$ обозначают наблюдаемую и скрытую переменные соответственно.\n",
        "\n",
        "### Нейронная сеть енкодера\n",
        "Эта сеть берет приблизительное апостериорное распределение $q(z|x)$, которое принимает в качестве ввода наблюдение и выводит набор параметров для определения условного распределения скрытого представления $z$.\n",
        "В этом примере мы просто моделируем распределение как диагональ гауссовских процессов, и сеть выводит средние и логарифмические отклонения факторизованного гаусса.\n",
        "Мы выводим логарифмическое отклонение вместо простого отклонения непосредственно для числовой устойчивости.\n",
        "\n",
        "### Нейронная сеть декодера\n",
        "Это сеть определяет условное распределение наблюдения $p(x|z)$, которое принимает скрытое представление $z$ в качестве входных данных и выводит параметры для условного распределения наблюдения.\n",
        "Мы моделируем скрытое априорное распределение $p(z)$ как гауссову единицу.\n",
        "\n",
        "### Трюк с повторной параметризацией\n",
        "Чтобы сгенерировать выборку $z$ для декодера во время обучения, мы можем сделать выборку из скрытого распределения, определенного параметрами, выводимыми енкодером, с учетом входного наблюдения $x$.\n",
        "Однако эта операция выборки создает узкое место, поскольку обратное распространение не может проходить через случайный узел.\n",
        "\n",
        "Чтобы решить эту проблему, мы используем прием повторной параметризации.\n",
        "В нашем примере мы аппроксимируем $z$ с использованием параметров декодера и дополнительного параметра $\\epsilon$ следующим образом:\n",
        "\n",
        "$$z = \\mu + \\sigma \\odot \\epsilon$$\n",
        "\n",
        "где $\\mu$ и $\\sigma$ представляют среднее и стандартное отклонение гауссовского распределения. Их можно получить из выходных данных декодера. $\\epsilon$ можно рассматривать как случайный шум, используемый для поддержания стохастичности $z$. Мы генерируем $\\epsilon$ из стандартного нормального распределения.\n",
        "\n",
        "Скрытая переменная $z$ теперь генерируется функцией $\\mu$, $\\sigma$ и $\\epsilon$, что позволяет модели распространять градиенты в декодере обратно через $\\mu$ и $\\sigma$ , сохраняя стохастичность через $\\epsilon$.\n",
        "\n",
        "### Сетевая архитектура\n",
        "Для сети енкодера мы используем два сверточных слоя, за которыми следует полносвязный слой. В сети декодера мы отражаем эту архитектуру, используя полносвязный слой, за которым следуют три сверточных слой и слой транспонирования(в некоторых случаях также называемые деконволюционными слоями). Обратите внимание, что при обучении VAE, обычно не используется пакетная нормализация, так как дополнительная случайность из-за использования мини-пакетов может увеличить нестабильность выборки.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "VGLbvBEmjK0a"
      },
      "outputs": [],
      "source": [
        "class CVAE(tf.keras.Model):\n",
        "  \"\"\"Convolutional variational autoencoder.\"\"\"\n",
        "\n",
        "  def __init__(self, latent_dim):\n",
        "    super(CVAE, self).__init__()\n",
        "    self.latent_dim = latent_dim\n",
        "    self.encoder = tf.keras.Sequential(\n",
        "        [\n",
        "            tf.keras.layers.InputLayer(input_shape=(28, 28, 1)),\n",
        "            tf.keras.layers.Conv2D(\n",
        "                filters=32, kernel_size=3, strides=(2, 2), activation='relu'),\n",
        "            tf.keras.layers.Conv2D(\n",
        "                filters=64, kernel_size=3, strides=(2, 2), activation='relu'),\n",
        "            tf.keras.layers.Flatten(),\n",
        "            # No activation\n",
        "            tf.keras.layers.Dense(latent_dim + latent_dim),\n",
        "        ]\n",
        "    )\n",
        "\n",
        "    self.decoder = tf.keras.Sequential(\n",
        "        [\n",
        "            tf.keras.layers.InputLayer(input_shape=(latent_dim,)),\n",
        "            tf.keras.layers.Dense(units=7*7*32, activation=tf.nn.relu),\n",
        "            tf.keras.layers.Reshape(target_shape=(7, 7, 32)),\n",
        "            tf.keras.layers.Conv2DTranspose(\n",
        "                filters=64, kernel_size=3, strides=2, padding='same',\n",
        "                activation='relu'),\n",
        "            tf.keras.layers.Conv2DTranspose(\n",
        "                filters=32, kernel_size=3, strides=2, padding='same',\n",
        "                activation='relu'),\n",
        "            # No activation\n",
        "            tf.keras.layers.Conv2DTranspose(\n",
        "                filters=1, kernel_size=3, strides=1, padding='same'),\n",
        "        ]\n",
        "    )\n",
        "\n",
        "  @tf.function\n",
        "  def sample(self, eps=None):\n",
        "    if eps is None:\n",
        "      eps = tf.random.normal(shape=(100, self.latent_dim))\n",
        "    return self.decode(eps, apply_sigmoid=True)\n",
        "\n",
        "  def encode(self, x):\n",
        "    mean, logvar = tf.split(self.encoder(x), num_or_size_splits=2, axis=1)\n",
        "    return mean, logvar\n",
        "\n",
        "  def reparameterize(self, mean, logvar):\n",
        "    eps = tf.random.normal(shape=mean.shape)\n",
        "    return eps * tf.exp(logvar * .5) + mean\n",
        "\n",
        "  def decode(self, z, apply_sigmoid=False):\n",
        "    logits = self.decoder(z)\n",
        "    if apply_sigmoid:\n",
        "      probs = tf.sigmoid(logits)\n",
        "      return probs\n",
        "    return logits"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0FMYgY_mPfTi"
      },
      "source": [
        "## Определение функции потерь и оптимайзера\n",
        "\n",
        "VAE обучается, максимизируя нижнюю границу доказательства(ELBO) на логарифме предельного правдоподобия:\n",
        "\n",
        "$$\\log p(x) \\ge \\text{ELBO} = \\mathbb{E}_{q(z|x)}\\left[\\log \\frac{p(x, z)}{q(z|x)}\\right].$$\n",
        "\n",
        "На практике мы оптимизируем оценку этого ожидания методом Монте-Карло для одной выборки:\n",
        "\n",
        "$$ \\ log p (x | z) + \\ log p (z) - \\ log q (z | x), $$\n",
        "где $ z $ выбирается из $ q (z | x) $.\n",
        "\n",
        "**Примечание**: мы также можем аналитически вычислить KL(расстояние Кульбака-Лейблера), но здесь мы включили все три параметра в оценщик Монте-Карло."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "iWCn_PVdEJZ7"
      },
      "outputs": [],
      "source": [
        "optimizer = tf.keras.optimizers.Adam(1e-4)\n",
        "\n",
        "\n",
        "def log_normal_pdf(sample, mean, logvar, raxis=1):\n",
        "  log2pi = tf.math.log(2. * np.pi)\n",
        "  return tf.reduce_sum(\n",
        "      -.5 * ((sample - mean) ** 2. * tf.exp(-logvar) + logvar + log2pi),\n",
        "      axis=raxis)\n",
        "\n",
        "\n",
        "def compute_loss(model, x):\n",
        "  mean, logvar = model.encode(x)\n",
        "  z = model.reparameterize(mean, logvar)\n",
        "  x_logit = model.decode(z)\n",
        "  cross_ent = tf.nn.sigmoid_cross_entropy_with_logits(logits=x_logit, labels=x)\n",
        "  logpx_z = -tf.reduce_sum(cross_ent, axis=[1, 2, 3])\n",
        "  logpz = log_normal_pdf(z, 0., 0.)\n",
        "  logqz_x = log_normal_pdf(z, mean, logvar)\n",
        "  return -tf.reduce_mean(logpx_z + logpz - logqz_x)\n",
        "\n",
        "\n",
        "@tf.function\n",
        "def train_step(model, x, optimizer):\n",
        "  \"\"\"\n",
        "    Выполняется на шаге обучение и возвращает величину потерь.\n",
        "    Эта функция вычисляет потери и градиенты и использует \n",
        "    последние для обновления параметров модели.\n",
        "  \"\"\"\n",
        "  with tf.GradientTape() as tape:\n",
        "    loss = compute_loss(model, x)\n",
        "  gradients = tape.gradient(loss, model.trainable_variables)\n",
        "  optimizer.apply_gradients(zip(gradients, model.trainable_variables))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Rw1fkAczTQYh"
      },
      "source": [
        "## Обучение\n",
        "\n",
        "* Мы начинаем с итерации по набору данных\n",
        "* Во время каждой итерации мы передаем изображение в енкодер, чтобы получить набор среднего и логарифмического приближенного распределения $q(z|x)$\n",
        "* Затем мы применяем трюк *повторной параметризации* к выборке из $q(z|x)$\n",
        "* Наконец, мы передаем повторно параметризованные выборки в декодер, чтобы получить вероятности генеративного распределения $p(x|z)$\n",
        "* **Примечание:** Поскольку мы используем датасет keras, с 60 тыс. примеров данных в обучающем датасете и 10 тыс. в тестовом датасете, наш результирующий ELBO на тестовом датасете немного выше, чем результаты, представленные в литературе, которая использует динамическую бинаризацию MNIST Larochelle.\n",
        "\n",
        "### Генерация изображений\n",
        "\n",
        "* После обучения пришло время сгенерировать несколько изображений\n",
        "* Мы начинаем с получения набора скрытых векторов из гауссовской априорной вероятности $p(z)$\n",
        "* Затем генератор преобразует скрытую выборку $z$ в вероятности наблюдения, давая распределение $p(x|z)$\n",
        "* И наконец мы выводим график вероятности распределений Бернулли.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "NS2GWywBbAWo"
      },
      "outputs": [],
      "source": [
        "epochs = 10\n",
        "# преобразовать размерность скрытого пространства в плоскость для последующей визуализации\n",
        "latent_dim = 2\n",
        "num_examples_to_generate = 16\n",
        "\n",
        "# keeping the random vector constant for generation (prediction) \n",
        "# so it will be easier to see the improvement.\n",
        "random_vector_for_generation = tf.random.normal(\n",
        "    shape=[num_examples_to_generate, latent_dim])\n",
        "model = CVAE(latent_dim)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "RmdVsmvhPxyy"
      },
      "outputs": [],
      "source": [
        "def generate_and_save_images(model, epoch, test_sample):\n",
        "  mean, logvar = model.encode(test_sample)\n",
        "  z = model.reparameterize(mean, logvar)\n",
        "  predictions = model.sample(z)\n",
        "  fig = plt.figure(figsize=(4, 4))\n",
        "\n",
        "  for i in range(predictions.shape[0]):\n",
        "    plt.subplot(4, 4, i + 1)\n",
        "    plt.imshow(predictions[i, :, :, 0], cmap='gray')\n",
        "    plt.axis('off')\n",
        "\n",
        "  plt.savefig('image_at_epoch_{:04d}.png'.format(epoch))\n",
        "  plt.show()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "swCyrbqQQ-Ri"
      },
      "outputs": [],
      "source": [
        "# Выберите образец из тестового датасета для генерации выходных изображений\n",
        "assert batch_size >= num_examples_to_generate\n",
        "for test_batch in test_dataset.take(1):\n",
        "  test_sample = test_batch[0:num_examples_to_generate, :, :, :]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "2M7LmLtGEMQJ"
      },
      "outputs": [],
      "source": [
        "generate_and_save_images(model, 0, test_sample)\n",
        "\n",
        "for epoch in range(1, epochs + 1):\n",
        "  start_time = time.time()\n",
        "  for train_x in train_dataset:\n",
        "    train_step(model, train_x, optimizer)\n",
        "  end_time = time.time()\n",
        "\n",
        "  loss = tf.keras.metrics.Mean()\n",
        "  for test_x in test_dataset:\n",
        "    loss(compute_loss(model, test_x))\n",
        "  elbo = -loss.result()\n",
        "  display.clear_output(wait=False)\n",
        "  print('Epoch: {}, Test set ELBO: {}, time elapse for current epoch: {}'\n",
        "        .format(epoch, elbo, end_time - start_time))\n",
        "  generate_and_save_images(model, epoch, test_sample)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "P4M_vIbUi7c0"
      },
      "source": [
        "### Отображение сгенерированного изображения из последней эпохи обучения"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "WfO5wCdclHGL"
      },
      "outputs": [],
      "source": [
        "def display_image(epoch_no):\n",
        "  return PIL.Image.open('image_at_epoch_{:04d}.png'.format(epoch_no))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "5x3q9_Oe5q0A"
      },
      "outputs": [],
      "source": [
        "plt.imshow(display_image(epoch))\n",
        "plt.axis('off')  # Display images"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "NywiH3nL8guF"
      },
      "source": [
        "### Отображение анимированног GIF-а всех сохраненных изображений"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "IGKQgENQ8lEI"
      },
      "outputs": [],
      "source": [
        "anim_file = 'cvae.gif'\n",
        "\n",
        "with imageio.get_writer(anim_file, mode='I') as writer:\n",
        "  filenames = glob.glob('image*.png')\n",
        "  filenames = sorted(filenames)\n",
        "  for filename in filenames:\n",
        "    image = imageio.imread(filename)\n",
        "    writer.append_data(image)\n",
        "  image = imageio.imread(filename)\n",
        "  writer.append_data(image)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "2ZqAEtdqUmJF"
      },
      "outputs": [],
      "source": [
        "import tensorflow_docs.vis.embed as embed\n",
        "embed.embed_file(anim_file)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PeunRU6TSumT"
      },
      "source": [
        "### Отображение двумерного множества цифр из скрытого пространства\n",
        "\n",
        "Выполнение приведенного ниже кода покажет непрерывное распределение различных классов цифр, при этом каждая цифра трансформируется в другую в двухмерном скрытом пространстве. Мы используем [TensorFlow Probability](https://www.tensorflow.org/probability), чтобы сгенерировать стандартное нормальное распределение для скрытого пространства."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "code",
        "id": "mNcaaYPBS3mj"
      },
      "outputs": [],
      "source": [
        "def plot_latent_images(model, n, digit_size=28):\n",
        "  \"\"\"Plots n x n digit images decoded from the latent space.\"\"\"\n",
        "\n",
        "  norm = tfp.distributions.Normal(0, 1)\n",
        "  grid_x = norm.quantile(np.linspace(0.05, 0.95, n))\n",
        "  grid_y = norm.quantile(np.linspace(0.05, 0.95, n))\n",
        "  image_width = digit_size*n\n",
        "  image_height = image_width\n",
        "  image = np.zeros((image_height, image_width))\n",
        "\n",
        "  for i, yi in enumerate(grid_x):\n",
        "    for j, xi in enumerate(grid_y):\n",
        "      z = np.array([[xi, yi]])\n",
        "      x_decoded = model.sample(z)\n",
        "      digit = tf.reshape(x_decoded[0], (digit_size, digit_size))\n",
        "      image[i * digit_size: (i + 1) * digit_size,\n",
        "            j * digit_size: (j + 1) * digit_size] = digit.numpy()\n",
        "\n",
        "  plt.figure(figsize=(10, 10))\n",
        "  plt.imshow(image, cmap='Greys_r')\n",
        "  plt.axis('Off')\n",
        "  plt.show()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "F-ZG69QCZnGY"
      },
      "outputs": [],
      "source": [
        "plot_latent_images(model, 20)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "HrJRef8Ln945"
      },
      "source": [
        "## Следующие шаги\n",
        "\n",
        "В этом руководстве показано, как реализовать сверточный вариационный автоэнкодер с помощью TensorFlow.\n",
        "\n",
        "В качестве следующего шага вы можете попытаться улучшить вывод модели, увеличив размер сети.\n",
        "Например, вы можете попробовать установить параметры `filter` для каждого из слоев` Conv2D` и `Conv2DTranspose` на 512.\n",
        "Обратите внимание, что для генерации окончательного 2D-графика скрытого изображения вам нужно оставить для параметра `latent_dim` значение 2. Кроме того, время обучения будет увеличиваться с увеличением размера сети.\n",
        "\n",
        "Вы также можете попробовать реализовать VAE, используя другой набор данных, например CIFAR-10.\n",
        "\n",
        "VAE могут быть реализованы в нескольких различных стилях и различной сложности. Вы можете найти дополнительные реализации в следующих источниках:\n",
        "- [Вариационный автоэнкодер(keras.io)](https://keras.io/examples/generative/vae/)\n",
        "- [Пример VAE из руководства «Написание пользовательских слоев и моделей»(tensorflow.org)](https://www.tensorflow.org/guide/keras/custom_layers_and_models#putting_it_all_to General_an_end-to-end_example)\n",
        "- [Вероятностные слои TFP: вариационный автоэнкодер](https://www.tensorflow.org/probability/examples/Probabilistic_Layers_VAE)\n",
        "\n",
        "Если вы хотите узнать больше о VAE, обратитесь к [Введение в вариационные автоэнкодеры](https://arxiv.org/abs/1906.02691)."
      ]
    }
  ],
  "metadata": {
    "accelerator": "GPU",
    "colab": {
      "collapsed_sections": [],
      "name": "cvae.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
